1. Le patron Observer

observer

1.1. Motivation

observer illustration
Figure 1. L’illustration classique du patron Observer

1.2. Définition

Observateur définit une relation entre objets de type un-à-plusieurs, de façon que, lorsqu’un objet change d’état, tous ceux qui en dépendent en soient notifiés et soient mis à jour automatiquement.

observateur
Figure 2. Modèle UML du patron Observer

1.3. Application

Le patron Observer est utilisable dans de nombreuses situations :

  • Quand un concept a deux aspects, l’un dépendant de l’autre. Encapsuler ces aspects dans des objets séparés permet de les utiliser et les laisser évoluer de manière indépendante.

  • Dès que le changement d’un objet entraîne le changement de plusieurs autres.

  • Dès qu’un objet doit en notifier un certain nombre d’autres sans les connaitre.

1.4. Observer en Java

Java fournit des classes Observable/Observer pour le patron Observer. La classe java.util.Observable est la classe de base pour les sujets. Ainsi, toute classe qui veut être observée étant cette classe dont voici les caractéristiques :

  • fournit des méthodes pour ajouter/enlever des observateurs

  • fournit des méthodes pour notifier les observateurs

  • une sous-classe concrète doit seulement s’occuper de notifier à chque méthode modifiant l’état des objets (mutators)

  • utilise un vecteur stoquant les références des observateurs

L’interface java.util.Observer correspond aux observateurs qui doivent implémenter cette interface.

1.4.1. La classe java.util.Observable

Voici la liste des méthodes de java.util.Observable :

1 public Observable()
2 public synchronized void addObserver(Observer o)
3 protected synchronized void setChanged()
4 public synchronized void deleteObserver(Observer o)
5 protected synchronized void clearChanged()
6 public synchronized boolean hasChanged()
7 public void notifyObservers(Object arg)
8 public void notifyObservers()

1.4.2. L’interface java.util.Observer

java.util.Observer
 1 /**
 2 * This method is called whenever the observed object is changed. An
 3 * application calls an observable object's notifyObservers method to have all
 4 * the object's observers notified of the change.
 5 *
 6 * Parameters:
 7 * o - the observable object
 8 * arg - an argument passed to the notifyObservers method
 9 */
10 public abstract void update(Observable o, Object arg)

2. Une implémentation du MVC : les JTable java

JTable
Figure 3. Ceci n’est pas une JTable

2.2. L’architecture

JTable
Figure 4. Architecture de JTable

3. Le patron Adaptateur

adaptateur

3.1. Le problème

On veut pouvoir :

  • utiliser une classe existante, mais dont l’interface ne coïncide pas avec celle escomptée.

  • créer une classe réutilisable qui collabore avec des classes sans relations avec elle et encore inconnues, c’est-à-dire avec des classes qui n’auront pas nécessairement des interfaces compatibles.

  • vous avez besoin d’utiliser plusieurs sous-classes existantes, mais l'adaptation de leur interface par dérivation de chacune d’entre elles est impraticable. Un adaptateur objet peut adapter l’interface de sa classe parente.

    Ce dernier cas ne concerne que le cas "adaptateur d’objet"

3.2. Exemple concret : le retour des canards

  • L’existant :

public interface Canard {
  public void cancaner();
  public void voler();
}

public class Colvert implements Canard {
  public void cancaner() {
    System.out.println("Coincoin");
  }
  public void voler() {
    System.out.println("Je vole");
  }
}
  • Le "presque canard" :

public interface Dindon {
  public void glouglouter();
  public void voler();
}

public class DindonSauvage implements Dindon {
  public void glouglouter() {
    System.out.println("Glouglou");
  }
  public void voler() {
    System.out.println("Je ne vole pas loin");
  }
}

Vous êtes à court d’objets Canard et vous aimeriez utiliser des objets Dindon à la place!

public class AdaptateurDindon implements Canard {
  Dindon dindon;

  ...

  public void cancaner() {
    dindon.glouglouter();
  }

  public void voler() {
    // Adaptation du vol
    for(int i=0; i < 5; i++) {
      dindon.voler();
    }
  }
}

3.3. Le patron Adaptateur

Adaptateur (Adaptor) permet de convertir l’interface d’une classe en une autre conformément à l’attente du client. L’Adaptateur permet à des classes de collaborer, alors qu’elles n’auraient pas pu le faire du fait d’interfaces incompatibles.

adapteur
Figure 5. Modèle UML du patron Adaptateur
adaptor google
Figure 6. Adaptateur sur Google

4. Le patron Visiteur

les visiteurs

4.1. Le problème

Quelques situations à problème :

  • Une structure d’objets contient beaucoup de classes différentes d’interfaces distinctes, et vous désirez réaliser des opérations sur ces objets qui dépendent de leurs classes concrètes.

  • Il s’agit d’effectuer plusieurs opérations distinctes et sans relation entre elles, sur les objets d’une structure, et ceci en évitant de polluer leurs classes avec ces opérations.

  • Les classes qui définissent la structure objet changent rarement, mais on doit souvent définir de nouvelles opérations sur cette structure.

4.2. Illustration

visitor1
Figure 7. Une structure classique (sans patron)

4.2.1. Step 1

Définir une interface pour représenter les éléments de la structure.

visitor2
Figure 8. Implémentation d’une hiérarchie
visitor3
Figure 9. Utilisation d’une interface
ComputerPart.java (extrait)
public interface ComputerPart {
   ...
}

4.2.2. Step 2

Anticiper l’utilisation du visiteur.

ComputerPartVisitor.java (extrait)
public interface ComputerPartVisitor {
	...
}
ComputerPart.java
public interface ComputerPart {
   public void accept(ComputerPartVisitor computerPartVisitor);
}

4.2.3. Step 3

Créer les classes concrètes qui implémentent l’interface.

Keyboard.java
public class Keyboard implements ComputerPart {

   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

…​

Computer.java
public class Computer implements ComputerPart {

   ComputerPart[] parts;

   public Computer(){
      parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
   }


   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      for (int i = 0; i < parts.length; i++) {
         parts[i].accept(computerPartVisitor);
      }
      computerPartVisitor.visit(this);
   }
}

4.2.4. Step 4

Définir l’interface pour représenter le visiteur.

ComputerPartVisitor.java
public interface ComputerPartVisitor {
	public void visit(Computer computer);
	public void visit(Mouse mouse);
	public void visit(Keyboard keyboard);
	public void visit(Monitor monitor);
}

4.2.5. Step 5

Créer des visiteurs concrets.

visitor4
Figure 10. Visiteur : l’interface
DisplayVisitor.java
public class DisplayVisitor implements ComputerPartVisitor {

   @Override
   public void visit(Computer computer) {
      System.out.println("Displaying Computer.");
   }

   @Override
   public void visit(Mouse mouse) {
      System.out.println("Displaying Mouse.");
   }

   @Override
   public void visit(Keyboard keyboard) {
      System.out.println("Displaying Keyboard.");
   }

   @Override
   public void visit(Monitor monitor) {
      System.out.println("Displaying Monitor.");
   }
}

4.2.6. Step 6

Utiliser le visiteur DisplayVisitor.

VisitorPatternDemo.java
public class VisitorPatternDemo {
   public static void main(String[] args) {

      ComputerPart computer = new Computer();
      computer.accept(new DisplayVisitor());
   }
}

4.2.7. Step 7 (final)

Verify the output.

Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.

4.3. Le patron Visiteur

Visiteur (Visitor) permet la représentation d’une opération applicable aux éléments d’une structure d’objet.

Il définit une nouvelle opération, sans qu’il soit nécessaire de modifier la classe des éléments sur lesquels elle agit.

visitor dc
Figure 11. Patron Visiteur : structure
visitor seq
Figure 12. Patron Visiteur : comportement
visitor google
Figure 13. Visiteur sur Google

4.4. Avantages/Inconvénients

Avantages :

  • Permet d’ajouter des opérations à la structure d’un Composite sans modifier la structure elle-même.

  • L'ajout de nouvelles opérations est relativement facile.

  • Le code des opérations exécutées par le Visiteur est centralisé.

Inconvénients :

  • L’encapsulation des classes du Composite est brisée.

  • Comme une fonction de navigation est impliquée, les modifications de la structure du Composite sont plus difficiles.

4.5. Exemples d’utilisation

  • calcul sur un ensemble structuré d’éléments

  • génération de rapports ou de code

  • …​

4.6. Exemple concret d’utilisation en Java

Exemple tiré de ce site.
public interface ItemElement {

  public int accept(ShoppingCartVisitor visitor);
}
public class Book implements ItemElement {

  private int price;
  private String isbnNumber;

  public Book(int cost, String isbn){
    this.price=cost;
    this.isbnNumber=isbn;
  }

  public int getPrice() {
    return price;
  }

  public String getIsbnNumber() {
    return isbnNumber;
  }

  @Override
  public int accept(ShoppingCartVisitor visitor) {
    return visitor.visit(this);
  }

}
public class Fruit implements ItemElement {

  private int pricePerKg;
  private int weight;
  private String name;

  public Fruit(int priceKg, int wt, String nm){
    this.pricePerKg=priceKg;
    this.weight=wt;
    this.name = nm;
  }

  public int getPricePerKg() {
    return pricePerKg;
  }

  public int getWeight() {
    return weight;
  }

  public String getName(){
    return this.name;
  }

  @Override
  public int accept(ShoppingCartVisitor visitor) {
    return visitor.visit(this);
  }
}
public interface ShoppingCartVisitor {

  int visit(Book book);
  int visit(Fruit fruit);
}
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {

  @Override
  public int visit(Book book) {
    int cost=0;
    //apply 5$ discount if book price is greater than 50
    if(book.getPrice() > 50){
      cost = book.getPrice()-5;
    } else cost = book.getPrice();
    System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost);
    return cost;
  }

  @Override
  public int visit(Fruit fruit) {
    int cost = fruit.getPricePerKg()*fruit.getWeight();
    System.out.println(fruit.getName() + " cost = "+cost);
    return cost;
  }
}
public class ShoppingCartClient {

  public static void main(String[] args) {
    ItemElement[] items = new ItemElement[]{new Book(20, "1234"),new Book(100, "5678"),
    new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};

    int total = calculatePrice(items);
    System.out.println("Total Cost = "+total);
  }

  private static int calculatePrice(ItemElement[] items) {
    ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
    int sum=0;
    for(ItemElement item : items){
      sum = sum + item.accept(visitor);
    }
    return sum;
  }

}
Book ISBN::1234 cost =20
Book ISBN::5678 cost =95
Banana cost = 20
Apple cost = 25
Total Cost = 160

5. Le patron proxy

5.1. Le problème

On a besoin de références à un objet, qui soient plus créatives et plus sophistiquées qu’un simple pointeur.

5.2. Le patron Proxy

Procuration (Proxy) fournit à un tiers un mandataire ou un remplaçant, pour contrôler l’accès à cet objet.

proxy
Figure 14. Modèle UML du patron Proxy
proxy google
Figure 15. Proxy sur Google

5.3. Utilisations

  • Une procuration à distance fournit un représentant local d’un objet situé dans un espace adresse différent.

  • Une procuration virtuelle crée des objets lourds à la demande.

  • Une procuration de protection contrôle l’accès à l’objet original. Les procurations de protection sont utiles quand les objets doivent satisfaire différents droits d’accès.

  • Une référence intelligente est le remplaçant d’un pointeur brut, qui réalise des opérations supplémentaires, lors de l’accès à l’objet. Quelques utilisations typiques sont :

    • décompte du nombre des références faites à un objet réel, de sorte que celui-ci puisse être libéré automatiquement, dés qu’il n’y a plus de références ;

    • charger en mémoire un objet persistant quand il est référencé pour la première fois ;

    • vérifier, avant d’y accéder, que l’objet réel est verrouillé, pour être sûr qu’aucun autre objet ne pourra le changer.

5.4. Exemple concret : RMI

Remote Method Invocation

import java.rmi.*;
public interface MonService extends Remote {
  public String direBonjour() throws RemoteException;
}
import java.rmi.*;
import java.rmi.server.*;

public class MonServiceImpl extends UnicastRemoteObject implements MonService {
  public String direBonjour() {
    return "Le serveur dit 'Bonjour'";
  }
  public MonServiceImpl() throws RemoteException {}
  public static void main (String[] args) {
    try {
      MonService service = new MonServiceImpl();
      Naming.rebind("BonjourDistant", service);
    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }
}
MonService service =
  (MonService) Naming.lookup("rmi://127.0.0.1/BonjourDistant");
...
service.direBonjour();

6. Le patron Itérateur

6.1. Le problème

On veut pouvoir :

  • pour accéder au contenu d’un objet d’un agrégat sans en révéler la représentation interne ;

  • pour gérer simultanément plusieurs parcours dans des agrégats d’objets ;

  • pour offrir une interface uniforme pour les parcours au travers de diverses structures agrégats (c’est-à-dire, pour permettre l’itération polymorphe).

6.2. Le patron Itérateur

Itérateur (Iterator) fournit un moyen d’accès séquentiel aux éléments d’un agrégat d’objets, sans mettre à découvert la représentation interne de celui-ci.

iterateur
Figure 16. Modèle UML du patron Itérateur
iterateur google
Figure 17. Iterateur sur Google

6.3. Exemple concret

# Saluer tout le monde
def say_hi
  if @names.nil?
    puts "..."
  elsif @names.respond_to?("each")
    # @names est une liste de noms : traitons-les uns par uns
    @names.each do |name|
      puts "Hello #{name}!"
    end
  else
    puts "Hello #{@names}!"
  end
end

7. Le patron Composite

7.1. Le problème

On veut pouvoir :

  • représenter des hiérarchies de l’individu.

  • que le client n’ait pas à se préoccuper de la différence entre "combinaisons d’objets" et "objets individuels". Les clients pourront traiter de façon uniforme tous les objets de la structure composite.

7.2. Le patron Composite

Composite permet de composer des objets en des structures arborescentes pour représenter des hiérarchies composant/composé. Permet au client de traiter d’une façon unique les objets et les combinaisons d’objets.

composite
Figure 18. Modèle UML du patron Composite
composite google
Figure 19. Composite sur Google

7.3. Exemple concret

import java.util.ArrayList;

interface Graphic {
    public void print();
}

class CompositeGraphic implements Graphic {

  private ArrayList<Graphic> mChildGraphics = new ArrayList<Graphic>();

  public void print() {
    for (Graphic graphic : mChildGraphics) {
      graphic.print();
    }
  }

  public void add(Graphic graphic) {
    mChildGraphics.add(graphic);
  }

  public void remove(Graphic graphic) {
    mChildGraphics.remove(graphic);
  }
}

7.4. un "Anti" exemple

Que pensez-vous de cette définition de Composite ?

anti composite
Figure 20. Patron abîmé composite
On appelle ces modèles des "Patrons abîmés" (anti-patterns).

8. Retour sur le refactoring Banque

8.1. Le problème

refactoring1

Remplacer tous ces switch cases

continuer = true;
while (continuer) {
  AAB.afficherMenu(monAg);
  choix = lect.next();
  choix = choix.toLowerCase();
  switch (choix) {
    ...
    case "p" :
      System.out.print("Propriétaire -> ");
      nom = lect.next();
      AAB.comptesDUnPropretaire (monAg, nom);
    break;
    ...
  • Afficher une liste séparemment du switch

    //AAB.afficherMenu(monAg);
      System.out.println("Menu de " + ag.getNomAgence() + " (" + ag.getLocAgence() + ")");
      System.out.println("l - Liste des comptes de l'agence");
      ...
      System.out.println("p - voir les comptes d'un Propriétaire (par son nom)");
      ...
      System.out.print("Choix -> ");
    }
  • Tester tous les choix pour actionner la bonne option

      ...
      case "p" :
        System.out.print("Propriétaire -> ");
        nom = lect.next();
        AAB.comptesDUnPropretaire (monAg, nom);
      break;
      ...

8.2. Une solution

  • Des listes

  • Des options de menu qui encapsulent l’action à réaliser

public interface ActionList extends Action {

  public String listTitle();
  public int size();

  public boolean addAction(Action ac);
  public boolean removeAction(Action ac);

  public String[] listOfActions() ;

}
Une interface pour les options de menu
public interface Action  {

  public String actionMessage ();
  public void execute(AgenceBancaire ag);
}
Une classe concrète par option de menu
public class Action1 implements Action {

  private String lineMessage;

  ...
  public String actionMessage() {
    return this.lineMessage;
  }

  public void execute(AgenceBancaire ab) {

    ...
    ab.afficher();
  }
}
Utilisation de l’action
  action.execute(ab);
Lien entre liste et action
Action a1 = new Action1("Liste des comptes de l'agence");
Action a2 = new Action2("Voir un compte (par son numéro)");
Action a3 = new Action3(...);

ActionList al1 = new ActionListAgenceBancaire("Menu Général");

al1.addAction(a1);
al1.addAction(a2);
Lien entre liste et action : choix dans la liste
public void execute(AgenceBancaire ab) throws Exception {
  ...
  while (true) {
    this.printMenu();

    choice = this.readResponse();
    ...
    this.myMenu.get(choice).execute(ab);
    ...
La liste peut elle-même être une option de menu (une action)!
public interface ActionList extends Action {
}

9. Pour aller plus loin avec les patrons…​

plusloin

9.1. Partagez votre vocabulaire

  • Dans les réunions de conception (pas nécessairement avec le client)

  • Avec les autres développeurs

  • Dans la documentation de votre architecture

  • Dans les commentaires du code et les conventions de nommage

  • Dans les groupes/blogs de développeurs

  • (pas pendant les exams!)

9.2. Ne foncez pas tête baissée

Quelques conseils :

  • Les patterns sont des outils, non des règles.

    ⇒ Rien n’empêche de les modifier et de les adapter à votre problème.

  • Ne visez l’extensibilité que si la question se pose réellement dans la pratique, pas si elle est uniquement hypothétique.

  • Ne vous emballez pas et recherchez la simplicité.

    ⇒ Si vous trouvez une solution plus simple que l’emploi d’un pattern, n’hésitez pas !

  • Éliminez ce qui n’est pas vraiment nécessaire.

    ⇒ N’ayez pas peur de supprimer un design pattern inutile de votre conception.

9.3. Les autres types de patrons

Il n’y a pas que les 3 types de patrons que l’on a vu :

  • De création

  • Structurels

  • Comportementaux

Il y a par exemple :

  • Les patrons d’architecture

  • Les patrons d’application

  • Les patrons de domaine

  • Les patrons de processus

  • Les patrons d’organisation

  • Les patrons de conception d’interfaces utilisateur

9.4. Les anti-patrons

anti patrons
  • Des solutions souvent appliquées à tort à des problèmes récurrents

  • Décrit comment partir d’un problème pour arriver à une mauvaise solution

  • Vous dit pourquoi une mauvaise solution est attrayante

  • Suggère d’autres patrons applicables pouvant fournir de meilleures solutions

9.5. Tous les patrons qu’on a pas vu

Il y en a beaucoup :

  • Chaîne de responsabilité

  • Commande

  • Décorateur

  • Façade

  • Interprète

  • Médiateur

  • Mémento

  • Monteur

  • Patron de méthode

  • Poids-mouche

  • Pont

  • Prototype

9.6. Principale utilisation des patrons : refactoring!

⇒ Les 2 prochaines semaines vous allez refactorer une application :

  • en binôme

  • en testant

  • en documentant

  • en justifiant

  • en essayant pas à tout prix de caser le plus de patrons "au kilo"